msg_tool\scripts\bgi/
bp.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::{decode_to_string, encode_string};
6use anyhow::Result;
7use std::io::{Seek, SeekFrom};
8
9#[derive(Debug)]
10pub struct BGIBpScriptBuilder {}
12
13impl BGIBpScriptBuilder {
14 pub fn new() -> Self {
16 BGIBpScriptBuilder {}
17 }
18}
19
20impl ScriptBuilder for BGIBpScriptBuilder {
21 fn default_encoding(&self) -> Encoding {
22 Encoding::Cp932
23 }
24
25 fn build_script(
26 &self,
27 buf: Vec<u8>,
28 _filename: &str,
29 encoding: Encoding,
30 _archive_encoding: Encoding,
31 config: &ExtraConfig,
32 _archive: Option<&Box<dyn Script>>,
33 ) -> Result<Box<dyn Script>> {
34 Ok(Box::new(BGIBpScript::new(buf, encoding, config)?))
35 }
36
37 fn extensions(&self) -> &'static [&'static str] {
38 &["_bp"]
39 }
40
41 fn script_type(&self) -> &'static ScriptType {
42 &ScriptType::BGIBp
43 }
44}
45
46#[derive(Debug)]
47struct BpString {
48 offset_pos: usize,
49 text_offset: u16,
50}
51
52#[derive(Debug)]
53pub struct BGIBpScript {
55 data: MemReader,
56 header_size: u32,
57 strings: Vec<BpString>,
58 encoding: Encoding,
59}
60
61impl BGIBpScript {
62 pub fn new(buf: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
68 let mut reader = MemReader::new(buf);
69 let header_size = reader.read_u32()?;
70 let instr_size = reader.read_u32()?;
71 if header_size as usize + instr_size as usize != reader.data.len() {
72 return Err(anyhow::anyhow!("Invalid bp script file size"));
73 }
74 let mut last_instr_pos = 0;
75 reader.seek(SeekFrom::Start(header_size as u64))?;
76 let max_instr_len = reader.data.len();
77 while reader.pos < max_instr_len {
78 let instr = reader.peek_u8()?;
79 if instr == 0x17 {
80 let need_zero_bytes = 3 - reader.pos % 4;
81 let mut all_zero = true;
82 for i in 0..need_zero_bytes {
83 let b = reader.peek_u8_at(reader.pos as u64 + 1 + i as u64)?;
84 if b != 0 {
85 all_zero = false;
86 break;
87 }
88 }
89 if all_zero {
90 last_instr_pos = reader.pos;
91 reader.pos += 1 + need_zero_bytes;
92 continue;
93 }
94 }
95 reader.pos += 1;
96 }
97 if last_instr_pos == 0 {
98 return Err(anyhow::anyhow!("No end instruction found in bp script"));
99 }
100 reader.seek(SeekFrom::Start(header_size as u64))?;
101 let mut strings = Vec::new();
102 while reader.pos < last_instr_pos {
103 let ins = reader.read_u8()?;
104 if ins == 5 {
105 let text_offset = reader.peek_u16()?;
106 let text_address = reader.pos + text_offset as usize - 1;
107 if text_address >= last_instr_pos
108 && text_address < reader.data.len()
109 && (text_address <= last_instr_pos + 1 || reader.data[text_address - 1] == 0)
110 {
111 strings.push(BpString {
112 offset_pos: reader.pos,
113 text_offset,
114 });
115 reader.pos += 2;
116 }
117 }
118 }
119 return Ok(BGIBpScript {
120 data: reader,
121 header_size,
122 strings,
123 encoding,
124 });
125 }
126}
127
128impl Script for BGIBpScript {
129 fn default_output_script_type(&self) -> OutputScriptType {
130 OutputScriptType::Json
131 }
132
133 fn default_format_type(&self) -> FormatOptions {
134 FormatOptions::None
135 }
136
137 fn extract_messages(&self) -> Result<Vec<Message>> {
138 let mut messages = Vec::new();
139 for i in self.strings.iter() {
140 let text_address = i.offset_pos + i.text_offset as usize - 1;
141 let str = self.data.cpeek_cstring_at(text_address as u64)?;
143 let str = decode_to_string(self.encoding, str.as_bytes(), true)?;
144 messages.push(Message {
145 name: None,
146 message: str,
147 });
148 }
149 Ok(messages)
150 }
151
152 fn import_messages<'a>(
153 &'a self,
154 messages: Vec<Message>,
155 mut file: Box<dyn WriteSeek + 'a>,
156 _filename: &str,
157 encoding: Encoding,
158 replacement: Option<&'a ReplacementTable>,
159 ) -> Result<()> {
160 if messages.len() != self.strings.len() {
161 return Err(anyhow::anyhow!(
162 "Number of messages does not match the number of strings in the script"
163 ));
164 }
165 file.write_all(&self.data.data)?;
166 let mut new_pos = self.data.data.len();
167 for (i, mes) in self.strings.iter().zip(messages) {
168 let text_address = i.offset_pos + i.text_offset as usize - 1;
169 let old_str_len = self
170 .data
171 .cpeek_cstring_at(text_address as u64)?
172 .as_bytes_with_nul()
173 .len();
174 let mut str = mes.message;
175 if let Some(replacement) = replacement {
176 for (key, value) in replacement.map.iter() {
177 str = str.replace(key, value);
178 }
179 }
180 let mut str = encode_string(encoding, &str, false)?;
181 str.push(0); let new_str_len = str.len();
183 if new_str_len > old_str_len {
184 file.write_all(&str)?;
185 let new_text_offset = (new_pos - i.offset_pos + 1) as u16;
186 file.write_u16_at(i.offset_pos as u64, new_text_offset)?;
187 new_pos += new_str_len;
188 } else {
189 file.write_all_at(text_address as u64, &str)?;
190 }
191 }
192 let new_instr_size = (new_pos - self.header_size as usize) as u32;
193 file.write_u32_at(4, new_instr_size)?;
194 Ok(())
195 }
196}